package app.server.project.main.service.service.recharge;

import app.server.project.core.constants.Def;
import app.server.project.core.util.GlobalValChangeEvent;
import app.server.project.main.service.bo.TONRetryBO;
import app.server.project.main.service.bo.event.CreateLuckOrderEvent;
import app.server.project.main.service.bo.event.RechargeSuccessEvent;
import app.server.project.main.service.constants.GlobalCfgKey;
import app.server.project.main.service.constants.LuckType;
import app.server.project.main.service.db.dao.cfg.GlobalCfgDao;
import app.server.project.main.service.db.dao.cfg.RechargeCfgDao;
import app.server.project.main.service.db.dao.user.OrderDetailDao;
import app.server.project.main.service.db.dao.user.RechargeOrderDao;
import app.server.project.main.service.db.entity.user.OrderDetailEntity;
import app.server.project.main.service.db.entity.user.RechargeOrderEntity;
import app.server.project.main.service.dto.recharge.BuyTonReq;
import app.server.project.main.service.dto.recharge.ClientCloseReq;
import app.server.project.main.service.dto.recharge.TONOrderDto;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.ton.tonapi.sync.Tonapi;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;

@Service
@RequiredArgsConstructor
@Slf4j
public class TONRecharge {

    private final static int MAX_API_RETRY = 0;
    private final static int MAX_ORDER_RETRY = 50;
    private final static int More_ORDER_RETRY = 10;
    private final static int PERIOD = 2;
    private final static String ORDER_KEY = "text";
    private final static int Limit = 100;
    private final static int CACHE_TIME = 10 * 60 * 60 * 1000;

    private final RechargeCfgDao rechargeCfgDao;
    private final GlobalCfgDao globalCfgDao;
    private final RechargeOrderDao rechargeOrderDao;
    private final OrderDetailDao orderDetailDao;
    private final TaskScheduler taskScheduler;
    private final ApplicationEventPublisher applicationEventPublisher;
    // 0.001 ton币     一千万是0.001ton
    /**
     * 最多重试
     */
    private ConcurrentLinkedDeque<TONRetryBO> retryBOS = new ConcurrentLinkedDeque<>();
    private ConcurrentHashMap<Long, TONRetryBO> retryBOMap = new ConcurrentHashMap<>();
    private Tonapi tonapi;

    private TimedCache<Long, String> hashCache = CacheUtil.newTimedCache(CACHE_TIME);


    public TONOrderDto createTonOrder(BuyTonReq req, long userId) {
        var ret = new TONOrderDto();
        var cfg = this.rechargeCfgDao.getDataById(req.getRechargeId());

        //创建订单
        var orderId = IdUtil.getSnowflakeNextId();
        ret.setOrderId(String.valueOf(orderId));
        var order = RechargeOrderEntity.of(orderId, userId, req.getRechargeId(), RechargeType.TON, cfg.getConsumeNum());
        //创建明细
        var tonOrder = OrderDetailEntity.of(orderId);
        tonOrder.setWalletAddress(req.getUserWalletAddress());
        TONRetryBO tonRetryBO = TONRetryBO.of(order, tonOrder, Def.Zero);
        //进入查询队列
        retryBOS.addLast(tonRetryBO);
        //订单缓存
        this.retryBOMap.put(orderId, tonRetryBO);
        if (StrUtil.isNotBlank(req.getChoice())) {
            if (req.getOrderType() == LuckType.BTC) {
                if (req.getChoice().length() != 4) {
                    return ret;
                }
            }
            if (req.getOrderType() == LuckType.SOL) {
                if (req.getChoice().length() != 1) {
                    return ret;
                }
            }
            //发送创建抽奖订单事件
            this.applicationEventPublisher.publishEvent(CreateLuckOrderEvent.of(userId, req.getChoice(), orderId, req.getOrderType()));
        }
        return ret;
    }


    @EventListener(value = {ApplicationReadyEvent.class, GlobalValChangeEvent.class})
    public void init() {
        var apiKey = this.globalCfgDao.getOneByKey(GlobalCfgKey.TON_API_KEY);
        boolean debug = false;
        if (this.globalCfgDao.has(GlobalCfgKey.TON_SAN_BOX)) {
            debug = this.globalCfgDao.getOneByKey(GlobalCfgKey.TON_SAN_BOX).getBool();
        }
        log.info("ton环境变量变更,sanbox:{} apikey:{}", debug, apiKey);
        this.tonapi = new Tonapi(apiKey.getVal(), debug, MAX_API_RETRY);
        this.initOrderTask();
    }

    public void initOrderTask() {
        //加载未到账成功订单
        this.rechargeOrderDao.getWaitOrders(RechargeType.TON).forEach(v -> {
            v.setSynDb(true);
            var tonOrder = this.orderDetailDao.getById(v.getId());
            if (Objects.isNull(tonOrder)) return;
            tonOrder.setSynDb(true);
            var retryBo = TONRetryBO.of(v, tonOrder, Def.Zero);
            retryBOS.addLast(retryBo);
            this.retryBOMap.put(v.getId(), retryBo);
        });
        this.taskScheduler.schedule(this::startTask, Instant.now().plusSeconds(PERIOD));
    }

    private void startTask() {
        try {
            this.chkOrder();
        } catch (Exception e) {
            log.error("ton订单处理异常", e);
        }

        var time = Instant.now().plusSeconds(PERIOD);
        //每隔一分钟开始检查到账情况
        this.taskScheduler.schedule(this::startTask, time);
    }


    private synchronized boolean hasCache(long orderId) {
        if (hashCache.containsKey(orderId)) {
            return true;
        }
        OrderDetailEntity orderDetailEntity = this.orderDetailDao.getOneByHash(String.valueOf(orderId));
        if (Objects.nonNull(orderDetailEntity)) {
            return true;
        }
        return false;
    }

    private void chkOrder() {
        //根据api速率查询
        var rateStr = this.globalCfgDao.getStr(GlobalCfgKey.TON_API_RATE_LIMIT);
        int rateLimit = 1;
        if (StrUtil.isNotBlank(rateStr)) {
            rateLimit = Integer.parseInt(rateStr);
        }
        var waitLst = new ArrayList<TONRetryBO>();
        while (rateLimit > 0) {
            var val = this.retryBOS.pollFirst();
            if (val == null) {
                break;
            }
            waitLst.add(val);
            rateLimit--;
        }
        if (waitLst.isEmpty()) {
            return;
        }
        String desStr = this.globalCfgDao.getStr(GlobalCfgKey.TON_DES);
        waitLst.forEach(tonRetryBO -> {
            try {

                //订单应该要在创建一个小时之内,完成支付
                var accountTransactions = this.tonapi.getBlockchain().getAccountTransactions(tonRetryBO.getOrderDetailEntity().getWalletAddress(), (long) Def.Zero, (long) Def.Zero, Limit);
                for (int i = 0; i < accountTransactions.getTransactions().size(); i++) {
                    var tx = accountTransactions.getTransactions().get(i);
                    if (CollectionUtil.isEmpty(tx.getOutMsgs())) continue;
                    var first = tx.getOutMsgs().get(0);
                    if (Objects.isNull(first.getDecodedBody())) continue;
                    var orderIdStr = first.getDecodedBody().get(ORDER_KEY);
                    if (Objects.isNull(orderIdStr)) continue;
                    if (!NumberUtil.isLong(String.valueOf(orderIdStr))) continue;
                    //开始检查目标地址
                    var userFriendly = first.getDestination().getAddress().getValue();
                    if (!desStr.equals(userFriendly)) continue;
                    long orderId = Long.parseLong(String.valueOf(orderIdStr));
                    //检查缓存
                    if (this.hasCache(orderId)) continue;

                    if (tonRetryBO.getRechargeOrderEntity().getId().longValue() == orderId && tonRetryBO.getRechargeOrderEntity().getConsumeNum() >= first.getValue()) {
                        var cfg = this.rechargeCfgDao.getDataById(tonRetryBO.getRechargeOrderEntity().getRechargeCfgId());
                        //匹配到订单
                        //更新主表
                        RechargeOrderEntity orderEntity = RechargeOrderEntity.of(tonRetryBO.getRechargeOrderEntity().getId(),
                                tonRetryBO.getRechargeOrderEntity().getUserId(),
                                tonRetryBO.getRechargeOrderEntity().getRechargeCfgId(),
                                RechargeType.TON,
                                cfg.getConsumeNum()
                        );
                        orderEntity.setStatus(PayStatus.Finish);
                        //更新副表
                        OrderDetailEntity tonOrder = OrderDetailEntity.of(tonRetryBO.getRechargeOrderEntity().getId());
                        //更新数据库
                        tonOrder.setTonHash(String.valueOf(orderId));
                        //更新缓存
                        this.hashCache.put(orderId, String.valueOf(orderId));
                        log.info("用户:{} 订单:{} hash:{} ton 充值成功",
                                tonRetryBO.getRechargeOrderEntity().getUserId(),
                                orderEntity.getId(),
                                tonOrder.getTonHash());

                        this.applicationEventPublisher.publishEvent(RechargeSuccessEvent.createTon(tonRetryBO.getRechargeOrderEntity().getUserId(), cfg, tonRetryBO.getRechargeOrderEntity(), first.getCreatedAt()));
                        return;
                    }
                }
            } catch (Exception e) {
                log.error("开始查询ton 交易记录出现错误", e);
            }
            tonRetryBO.addRetry();
            //检查重试次数是否过多
            if (tonRetryBO.getMaxRetry() >= MAX_ORDER_RETRY) {
                this.closeOrder(tonRetryBO);
            } else {
                this.retryBOS.addLast(tonRetryBO);
            }
        });
    }

    @Async(Def.CommonPool)
    public void clientCloseOrder(ClientCloseReq req) {
        var retryBO = this.retryBOMap.get(req.getOrderId());
        if (Objects.isNull(retryBO)) {
            return;
        }
        var order = retryBO.getRechargeOrderEntity();
        var tonOrder = retryBO.getOrderDetailEntity();
        tonOrder.setFailDes(req.getFail());
        if (order.getStatus() == PayStatus.Wait) {
            order.setStatus(PayStatus.ClientClose);
        }
        retryBO.setMaxRetry(More_ORDER_RETRY);
    }

    private void closeOrder(TONRetryBO tonRetryBO) {
        var order = tonRetryBO.getRechargeOrderEntity();
        this.retryBOMap.remove(order.getId());
        if (order.getStatus() != PayStatus.Wait) return;
        order.setStatus(PayStatus.Close);
    }
}
